package com.demo.web3j.utils;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.DefaultBlockParameterNumber;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthEstimateGas;
import org.web3j.protocol.core.methods.response.EthGasPrice;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * web3j工具
 * 面向修改关闭面向拓展开放 以后需要增加新的节点RPC只用配置文件加上对应的字段且多加一个get方法即可
 */
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "chain")
public class Web3jUtils {


    //rpc节点列表
    private HashMap<String,String> rpcUrlMap;

    //web3j map
    private HashMap<String,Web3j> web3jHashMap = new HashMap<>();

    public void initWeb3j(){
        for (String key : rpcUrlMap.keySet()) {
            web3jHashMap.put(key,Web3j.build(new HttpService(rpcUrlMap.get(key))));
        }
    }

    /**
     * 动态拿到gas价格
     * @param web3j
     * @return
     */
    public BigInteger getGasPrice(Web3j web3j){
        BigInteger gasPrice=null;
        try {
            EthGasPrice send = web3j.ethGasPrice().send();
            gasPrice=send.getGasPrice();
        } catch (IOException e) {
            log.error("can't get gasPrice from private chain ,load default gasPrice:[{}]，exception log：{}",gasPrice,e);
        }
        return gasPrice;
    }

    /**
     * 动态获取本次执行的交易需要耗费的gasLimit
     * @param web3j web3j对象
     * @param from 操作账户地址
     * @param to 要操作的智能合约的地址
     * @param data 要操作的方法加密串
     * @return
     * @throws Exception
     */
    //estimateGasLimit函数用于计算本次执行的交易需要耗费的GasLimit，实际就是把你的交易数据传到链上模拟执行函数但不提交结果，将模拟执行花费的Gas作为请求结果返回
    //当拿到交易所需的所有数据，需要用私钥对数据进行签名操作方可执行
    public BigInteger estimateGasLimit(Web3j web3j, String from ,String to, String data) throws Exception {
        BigInteger gasLimit;
        Transaction testTransaction = Transaction.createFunctionCallTransaction(from, null, null, null, to, data);
        EthEstimateGas ethEstimateGas = web3j.ethEstimateGas(testTransaction).send();
        if (ethEstimateGas.hasError()) {
            log.error("动态获取本次执行的交易需要耗费的gasLimit error:",ethEstimateGas.getError().getMessage());
            throw new Exception("动态获取本次执行的交易需要耗费的gasLimit error: " + ethEstimateGas.getError().getMessage());
        }
        gasLimit = ethEstimateGas.getAmountUsed();
        return gasLimit;
    }


    /**
     * 动态拿到当前账户的nonce值  nonce值用于记录某个账户交易成功数量
     * @param web3j web3j对象
     * @param address 账户address
     * @return
     * 参考
     * https://blog.csdn.net/qq_36263544/article/details/128482389
     * https://ganhua.work/archives/web3j-nonce.html
     */
    public BigInteger getNonce(Web3j web3j, String address) {
        try {
            EthGetTransactionCount getNonce = web3j.ethGetTransactionCount(address, DefaultBlockParameterName.PENDING).send();

            if (getNonce == null){
                throw new RuntimeException("net error");
            }
            return getNonce.getTransactionCount();
        } catch (IOException e) {
            throw new RuntimeException("net error");
        }
    }

    /**
     * 得到事件日志
     * @param web3j web3j对象
     * @param start 起始区块
     * @param end   结束区块
     * @param list  合约地址列表
     * @param eventHash 事件编码哈希
     * @param optionalTopics    事件的topic哈希值
     * @return
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws IOException
     */
    public EthLog getEthLogs(Web3j web3j, BigInteger start, BigInteger end, List<String> list,String eventHash,String optionalTopics) throws InterruptedException, ExecutionException, IOException {
        EthFilter filter = new EthFilter(new DefaultBlockParameterNumber(start), new DefaultBlockParameterNumber(end),
                list);
        //如需通过事件过滤
        if (StrUtil.isNotBlank(eventHash)) {
            filter.addSingleTopic(eventHash);
        }
        //如需事件的topic哈希值过滤
        if(StrUtil.isNotBlank(optionalTopics)){
            filter.addOptionalTopics(optionalTopics);
        }
        EthLog log = web3j.ethGetLogs(filter).send();
        return log;
    }

    /**
     * 校验evm地址合法性
     * @param input
     * @return
     */
    public static boolean isETHValidAddress(String input) {
        if (StrUtil.isEmpty(input) || !input.startsWith("0x"))
            return false;
        return isValidAddress(input);
    }
    public static boolean isValidAddress(String input) {
        String cleanInput = Numeric.cleanHexPrefix(input);
        try {
            Numeric.toBigIntNoPrefix(cleanInput);
        } catch (NumberFormatException e) {
            return false;
        }
        return cleanInput.length() == 40;
    }


    public void setRpcUrlMap(HashMap<String, String> rpcUrlMap) {
        this.rpcUrlMap = rpcUrlMap;
    }

    /**
     * 得到bsc测试链web3j对象
     * @return
     */
    public Web3j getBscWeb3j() {
        return web3jHashMap.get("bscTest");
    }




}
